import numpy as np
import sys, os
sys.path.append(os.pardir)
from dataset.mnist import load_mnist

# 神经网络的“学习”是指从训练数据中自动获取最优权重参数的过程。
# 只对某个数据集过度拟合的状态称为过拟合（over fitting）。
# 神经网络以某个指标为线索寻找最优权重参数。神经网络的学习中
# 所用的指标称为损失函数（loss function）。这个损失函数可以使用任意函数，
# 但一般用均方误差和交叉熵误差等。

# 均方误差
def mean_squared_error(y,t):
    return 0.5*np.sum((y-t)**2)

t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0] # t_k表示监督数据,k表示数据的维数
y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0] # 神经网络的输出y是softmax函数的输出
# print(mean_squared_error(np.array(y), np.array(t)))
# print(np.array(t).shape[0])
# print(np.array(y).ndim)
# print(np.array(t).size)
# 交叉熵误差
# def cross_entropy_error(y,t):
#     delta = 1e-7
#     return -np.sum(t*np.log(y+delta))
#  添加一个delta防止出现log(0)
def cross_entropy_error(y,t): # 实现对应 mini-batch 的交叉熵误差
    if y.ndim == 1:
        t = t.rehsape(1,t.size) # 转化为1*t.size大小的数组
        y = y.reshape(1,y.size) # 转化为1*y.size大小的数组
    batch_size = y.shape[0]
    return -np.sum(t*np.log(y+1e-7))/batch_size
#print(cross_entropy_error(np.array(y),np.array(t)))

# mini-batch：
# 以全部数据为对象求损失函数的和，则计算过程需要花费较长的时间。再者，如果遇到大数据，数据量会有几百万、几千万之多，
# 这种情况下以全部数据为对象计算损失函数是不现实的。因此，我们从全部数据中选出一部分，作为全部数据的“近似”。
# 神经网络的学习也是从训练数据中选出一批数据（称为mini-batch,小批量），然后对每个mini-batch进行学习。

(x_train,t_train),(x_test,t_test) = load_mnist(normalize=True,one_hot_label=True)

# print(x_train.shape)
# print(x_test.shape)
# print(x_train.shape[0])

# 从这个训练数据中随机抽取10笔数据
# 使用 np.random.choice() 可以从指定的数字中随机选择想要的数字。比如,
# np.random.choice(60000, 10) 会从 0 到 59999 之间随机选择 10 个数字。

# train_size = x_train.shape[0]
# batch_size = 10
# batch_mask = np.random.choice(train_size,batch_size)
# x_batch = x_train[batch_mask]
# t_batch = t_train[batch_mask]

# 我们只需指定这些随机选出的索引,取出 mini-batch,然后使用
# 这个 mini-batch 计算损失函数即可。

# 为何要设定损失函数
# 在神经网络的学习中,寻找最优参数(权重和偏置)时,要寻找使损失函数的值尽可能小的参数。为了找到使损失函数的值尽可能小
# 的地方,需要计算参数的导数(确切地讲是梯度),然后以这个导数为指引,逐步更新参数的值。
# 假设有一个神经网络,现在我们来关注这个神经网络中的某一个权重参数。此时,对该权重参数的损失函数求导,表示的是“如果稍微改变这个权
# 重参数的值,损失函数的值会如何变化”。如果导数的值为负,通过使该权重参数向正方向改变,可以减小损失函数的值;反过来,如果导数的值为正,
# 则通过使该权重参数向负方向改变,可以减小损失函数的值。不过,当导数的值为 0 时,无论权重参数向哪个方向变化,损失函数的值都不会改变,此
# 时该权重参数的更新会停在此处。之所以不能用识别精度作为指标,是因为这样一来绝大多数地方的导数都会变为 0,导致参数无法更新。
# 识别精度对微小的参数变化基本上没有什么反应,即便有反应,它的值也是不连续地、突然地变化。作为激活函数的阶跃函数也有同样的情况。出
# 于相同的原因,如果使用阶跃函数作为激活函数,神经网络的学习将无法进行。
# 阶跃函数就像“竹筒敲石”一样,只在某个瞬间产生变化。而 sigmoid 函数,不仅函数的输出(竖轴的值)是连续变化的,曲线的斜率(导数)
# 也是连续变化的。也就是说,sigmoid 函数的导数在任何地方都不为 0。这对神经网络的学习非常重要。得益于这个斜率不会为 0 的性质,神经网络的学
# 习得以正确进行。

